////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   statobjmandraw.cpp
//  Version:     v1.00
//  Created:     28/5/2001 by Vladimir Kajalin
//  Compilers:   Visual Studio.NET
//  Description: Render all entities in the sector together with shadows
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include <ICryAnimation.h>

#include "terrain.h"
#include "StatObj.h"
#include "ObjMan.h"
#include "VisAreas.h"
#include "terrain_sector.h"
#include "3dEngine.h"
#include "CullBuffer.h"
#include "3dEngine.h"
#include "CryParticleSpawnInfo.h"
#include "LightEntity.h"
#include "DecalManager.h"
#include "ObjectsTree.h"
#include "Brush.h"

#ifndef CAFE
uint8 BoxSides[0x40*8] = {
	0,0,0,0, 0,0,0,0, //00
	0,4,6,2, 0,0,0,4, //01
	7,5,1,3, 0,0,0,4, //02
	0,0,0,0, 0,0,0,0, //03
	0,1,5,4, 0,0,0,4, //04
	0,1,5,4, 6,2,0,6, //05
	7,5,4,0, 1,3,0,6, //06
	0,0,0,0, 0,0,0,0, //07
	7,3,2,6, 0,0,0,4, //08
	0,4,6,7, 3,2,0,6, //09
	7,5,1,3, 2,6,0,6, //0a
	0,0,0,0, 0,0,0,0, //0b
	0,0,0,0, 0,0,0,0, //0c
	0,0,0,0, 0,0,0,0, //0d
	0,0,0,0, 0,0,0,0, //0e
	0,0,0,0, 0,0,0,0, //0f
	0,2,3,1, 0,0,0,4, //10
	0,4,6,2, 3,1,0,6, //11
	7,5,1,0, 2,3,0,6, //12
	0,0,0,0, 0,0,0,0, //13
	0,2,3,1, 5,4,0,6, //14
	1,5,4,6, 2,3,0,6, //15
	7,5,4,0, 2,3,0,6, //16
	0,0,0,0, 0,0,0,0, //17
	0,2,6,7, 3,1,0,6, //18
	0,4,6,7, 3,1,0,6, //19
	7,5,1,0, 2,6,0,6, //1a
	0,0,0,0, 0,0,0,0, //1b
	0,0,0,0, 0,0,0,0, //1c
	0,0,0,0, 0,0,0,0, //1d
	0,0,0,0, 0,0,0,0, //1e
	0,0,0,0, 0,0,0,0, //1f
	7,6,4,5, 0,0,0,4, //20
	0,4,5,7, 6,2,0,6, //21
	7,6,4,5, 1,3,0,6, //22
	0,0,0,0, 0,0,0,0, //23
	7,6,4,0, 1,5,0,6, //24
	0,1,5,7, 6,2,0,6, //25
	7,6,4,0, 1,3,0,6, //26
	0,0,0,0, 0,0,0,0, //27
	7,3,2,6, 4,5,0,6, //28
	0,4,5,7, 3,2,0,6, //29
	6,4,5,1, 3,2,0,6, //2a
	0,0,0,0, 0,0,0,0, //2b
	0,0,0,0, 0,0,0,0, //2c
	0,0,0,0, 0,0,0,0, //2d
	0,0,0,0, 0,0,0,0, //2e
	0,0,0,0, 0,0,0,0, //2f
	0,0,0,0, 0,0,0,0, //30
	0,0,0,0, 0,0,0,0, //31
	0,0,0,0, 0,0,0,0, //32
	0,0,0,0, 0,0,0,0, //33
	0,0,0,0, 0,0,0,0, //34
	0,0,0,0, 0,0,0,0, //35
	0,0,0,0, 0,0,0,0, //36
	0,0,0,0, 0,0,0,0, //37
	0,0,0,0, 0,0,0,0, //38
	0,0,0,0, 0,0,0,0, //39
	0,0,0,0, 0,0,0,0, //3a
	0,0,0,0, 0,0,0,0, //3b
	0,0,0,0, 0,0,0,0, //3c
	0,0,0,0, 0,0,0,0, //3d
	0,0,0,0, 0,0,0,0, //3e
	0,0,0,0, 0,0,0,0, //3f
};
#endif

bool CObjManager::IsAfterWater(const Vec3 & vPos, const Vec3 & vCamPos, float fUserWaterLevel)
{
	float fWaterLevel = fUserWaterLevel==WATER_LEVEL_UNKNOWN && GetTerrain() ? GetTerrain()->GetWaterLevel() : fUserWaterLevel;
	return (0.5f-m_nRenderStackLevel)*(vCamPos.z-fWaterLevel)*(vPos.z-fWaterLevel) > 0;
}

void CObjManager::RenderOccluderIntoZBuffer(IRenderNode * pEnt, float fEntDistance, CCullBuffer & rCB, bool bCompletellyInFrustum)
{
	FUNCTION_PROFILER_3DENGINE;

#ifdef _DEBUG
	const char * szName = pEnt->GetName();
	const char * szClassName = pEnt->GetEntityClassName();
#endif // _DEBUG

	// do not draw if marked to be not drawn
	unsigned int nRndFlags = pEnt->GetRndFlags();
	if(nRndFlags&ERF_HIDDEN)
		return;
	
	// Mark this object as good occluder if not yet marked.
	pEnt->SetRndFlags( ERF_GOOD_OCCLUDER, true );

	EERType eERType = pEnt->GetRenderNodeType();
/*
	if(GetCVars()->e_back_objects_culling &&	eERType == eERType_Brush)
	{
		if(CullBackObject(pEnt, rCB.GetCamera().GetPosition(), pEnt->m_WSBBox.GetCenter()))
		{
			pEnt->m_pRNTmpData->userData.m_OcclState.nLastOccludedMainFrameID = GetMainFrameID();
			return;
		}
	}
*/
  // check cvars
  switch(eERType)
  {
  case eERType_Brush:
    if(!GetCVars()->e_Brushes) return;
    break;
  case eERType_Vegetation:
    if(!GetCVars()->e_Vegetation) return;
    break;
  case eERType_ParticleEmitter:
    if(!GetCVars()->e_Particles) return;
    break;
  case eERType_Decal:
    if(!GetCVars()->e_Decals) return;
    break;
  case eERType_VoxelObject:
    if(!GetCVars()->e_Voxel) return;
    break;
  case eERType_WaterWave:
    if(!GetCVars()->e_WaterWaves) return;
    break;
	case eERType_Road:
		if(!GetCVars()->e_Roads) return;
		break;
  default:
    if(!GetCVars()->e_Entities) return;
    break;
  }

  // detect bad objects
  float fEntLengthSquared = pEnt->GetBBox().GetSize().GetLengthSquared();
  if(eERType != eERType_Light || !_finite(fEntLengthSquared))
  {
    if(fEntLengthSquared > MAX_VALID_OBJECT_VOLUME || !_finite(fEntLengthSquared) || fEntLengthSquared<=0)
    {
      static float fLastPrintTime = 0;

      if(fLastPrintTime < m_fCurrTime-1.f)
      {
        Warning("CObjManager::RenderObject: Object has invalid bbox: %s,%s, Radius = %.2f, Center = (%.1f,%.1f,%.1f)",
          pEnt->GetName(), pEnt->GetEntityClassName(), cry_sqrtf(fEntLengthSquared)*0.5f, 
          pEnt->GetBBox().GetCenter().x, pEnt->GetBBox().GetCenter().y, pEnt->GetBBox().GetCenter().z);
        
        fLastPrintTime = m_fCurrTime;
      }

      return; // skip invalid objects - usually only objects with invalid very big scale will reach this point
    }
  }

  // allocate RNTmpData for potentially visible objects
  Get3DEngine()->CheckCreateRNTmpData(&pEnt->m_pRNTmpData, pEnt);

/*  { // avoid double processing
    int nFrameId = GetFrameID();
    if(pEnt->m_pRNTmpData->userData.m_nOccluderFrameId == nFrameId)
      return;
    pEnt->m_pRNTmpData->userData.m_nOccluderFrameId = nFrameId;
  }*/

	// check all possible occlusions for outdoor objects
	if(fEntLengthSquared && GetCVars()->e_Portals!=3)// && eERType == eERType_Brush)
	{
		// test occlusion of outdoor objects by mountains
		CStatObj * pStatObj = (CStatObj *)pEnt->GetEntityStatObj();
		if(pStatObj && m_fZoomFactor)
		{
			if(GetCVars()->e_CoverageBufferOccludersTestMinTrisNum < pStatObj->GetRenderTrisCount())
			{
				float fZoomedDistance = fEntDistance*m_fInvZoomFactor;

				//	uint32 nOcclMask = 0;
				//			if(GetCVars()->e_CoverageBuffer_per_object_test)
				//	nOcclMask |= OCCL_TEST_CBUFFER;

				if(IsBoxOccluded(pEnt->GetBBox(), fZoomedDistance, &pEnt->m_pRNTmpData->userData.m_OcclState, pEnt->GetEntityVisArea()!=NULL, eoot_OCCLUDER))
					return;
				/*			
				bool bRes = !rCB.IsBoxVisible_OCCLUDER(pEnt->m_WSBBox);

				if(&rCB == Get3DEngine()->GetCoverageBuffer())
				{
				pEnt->m_OcclState.bOccluded = bRes;
				if(pEnt->m_OcclState.bOccluded)
				pEnt->m_OcclState.nLastOccludedMainFrameID = GetMainFrameID();
				else
				pEnt->m_OcclState.nLastVisibleMainFrameID = GetMainFrameID();
				}

				if(bRes)
				return;*/
			}
		}
	}

	// mark as rendered in this frame
	//	pEnt->SetDrawFrame( GetFrameID(), m_nRenderStackLevel );

	// update scissor
	SRendParams DrawParams;  

	DrawParams.fDistance = fEntDistance;

	DrawParams.pRenderNode = pEnt;

	// ignore ambient color set by artist
	DrawParams.dwFObjFlags = 0;

	if(nRndFlags&ERF_SELECTED)
		DrawParams.dwFObjFlags |= FOB_SELECTED;

	DrawParams.m_ShadowMapCasters = 0;

	DrawParams.dwFObjFlags |= FOB_TRANS_MASK;

	if(GetCVars()->e_HwOcclusionCullingObjects)
	{
		GetRenderer()->EF_StartEf();  

    pEnt->Render(DrawParams);

		GetRenderer()->EF_EndEf3D(SHDF_ZPASS|SHDF_ZPASS_ONLY, -1, -1);
	}

	//	if(GetCVars()->e_HwOcclusionCullingObjects)
	//	pEnt->m_nOccluderFrameId = GetFrameID();

	// add occluders into c-buffer
	if(	!GetCVars()->e_CoverageBuffer )
		return;

	bool bMeshFound = false;

	for(int nEntSlot = 0; nEntSlot<8; nEntSlot++)
	{
		Matrix34A matParent;
		if(CStatObj * pMainStatObj = (CStatObj *)pEnt->GetEntityStatObj(nEntSlot, 0, &matParent, true))
			bMeshFound |= RenderStatObjIntoZBuffer(pMainStatObj, matParent, nRndFlags, bCompletellyInFrustum, rCB, pEnt->GetMaterial());
		else if(ICharacterInstance * pChar = (ICharacterInstance *)pEnt->GetEntityCharacter(nEntSlot, &matParent, true))
		{
			ISkeletonPose * pSkeletonPose = pChar->GetISkeletonPose();
			uint32 numJoints = pSkeletonPose->GetJointCount();

			// go thru list of bones
			for(uint32 i=0; i<numJoints; i++)
			{
				IStatObj * pStatObj = pSkeletonPose->GetStatObjOnJoint(i);
				if(!pStatObj || pStatObj->GetFlags()&STATIC_OBJECT_HIDDEN)
					continue;

				Matrix34A tm34 = matParent*Matrix34(pSkeletonPose->GetAbsJointByID(i));

				if(pStatObj->GetRenderMesh())
					bMeshFound |= RenderStatObjIntoZBuffer(pStatObj, tm34, nRndFlags, bCompletellyInFrustum, rCB, pEnt->GetMaterial());
				else
				{
					// render sub-objects of bone object
					if(int nCount = pStatObj->GetSubObjectCount())
					{
						for (int nSubObjectId=0; nSubObjectId<nCount; nSubObjectId++)
							if(IStatObj * pSubObj = pStatObj->GetSubObject(nSubObjectId)->pStatObj)
								bMeshFound |= RenderStatObjIntoZBuffer(pSubObj, tm34, nRndFlags, bCompletellyInFrustum, rCB, pEnt->GetMaterial());
					}
				}
			}
		}
	}

	if(!bMeshFound)
	{ // voxel objects
		Matrix34A matParent;
    matParent.SetIdentity();
		pEnt->GetEntityStatObj(0, 0, &matParent);

    int nLod = CObjManager::GetObjectLOD(DrawParams.fDistance, pEnt->GetLodRatioNormalized(), 
      pEnt->GetBBox().GetRadius() * 0.5f * GetFloatCVar(e_CoverageBufferOccludersLodRatio));
    
#define VOX_MAX_LODS_NUM 3

    if(nLod >= min(1+GetCVars()->e_VoxelLodsNum,VOX_MAX_LODS_NUM))
      nLod = min(1+GetCVars()->e_VoxelLodsNum,VOX_MAX_LODS_NUM) - 1;
    
    if(nLod<0)
      nLod=0;

    while(nLod && !pEnt->GetRenderMesh(nLod))
      nLod--;

		if(pEnt->GetRenderMesh(nLod))
			rCB.AddRenderMesh(pEnt->GetRenderMesh(nLod), &matParent, pEnt->GetMaterial(), (nRndFlags&ERF_OUTDOORONLY)!=0, bCompletellyInFrustum,false);
	}

	if(GetCVars()->e_CoverageBufferDebug == 16)
		DrawBBox(pEnt->GetBBox(), Col_Orange);
}

int CObjManager::GetObjectLOD(float fDistance, float fLodRatioNorm, float fRadius)
{
	int nLodLevel = (int)(fDistance*(fLodRatioNorm*fLodRatioNorm)/(max(GetCVars()->e_LodRatio*min(fRadius, GetFloatCVar(e_LodCompMaxSize)),0.001f)));
	return nLodLevel;
}

float CObjManager::GetObjectLODDist(int nLodLevel, float fLodRatioNorm, float fRadius)
{
	// see above, we're getting distance for X lod level.
	//float fDistance = (float)nLodLevel/((fLodRatioNorm*fLodRatioNorm)/(max(GetCVars()->e_LodRatio*min(fRadius, GetFloatCVar(e_LodCompMaxSize)),0.001f)));
	float fDistance = (float)nLodLevel*(max(GetCVars()->e_LodRatio*min(fRadius, GetFloatCVar(e_LodCompMaxSize)),0.001f))/(fLodRatioNorm*fLodRatioNorm);
	return fDistance;
}

void CObjManager::FillTerrainTexInfo(IOctreeNode * pOcNode, float fEntDistance, struct SSectorTextureSet * & pTerrainTexInfo, const AABB & objBox)
{
  IVisArea * pVisArea = pOcNode->m_pVisArea;
  CTerrainNode * pTerrainNode = pOcNode->GetTerrainNode();

  if((!pVisArea || pVisArea->IsAffectedByOutLights()) && pTerrainNode)
  { // provide terrain texture info
    AABB boxCenter; boxCenter.min = boxCenter.max = objBox.GetCenter();
    if(CTerrainNode * pTerNode = pTerrainNode)
      if(pTerNode = pTerNode->FindMinNodeContainingBox(boxCenter))
      {
        while((!pTerNode->m_nNodeTexSet.nTex0 || 
          fEntDistance*2.f*8.f > (pTerNode->m_boxHeigtmapLocal.max.x-pTerNode->m_boxHeigtmapLocal.min.x))
          && pTerNode->m_pParent )
          pTerNode = pTerNode->m_pParent;

        pTerrainTexInfo = &(pTerNode->m_nNodeTexSet);
      }
  }
}

void CObjManager::RenderBrush( CBrush * pEnt, PodArray<CDLight*> * pAffectingLights,
                               const AABB & objBox, 
                               float fEntDistance, 
															 bool bSunOnly, int8 nThreadId, CVisArea * pVisArea, bool nCheckOcclusion, int nEngineFrameID)
{

  //	FUNCTION_PROFILER_3DENGINE;
	const CVars * pCVars = GetCVars();

#ifdef _DEBUG
  const char * szName = pEnt->GetName();
  const char * szClassName = pEnt->GetEntityClassName();
#endif // _DEBUG

  if(pEnt->m_dwRndFlags&ERF_HIDDEN)
    return;

  // check cvars
  assert(GetCVars()->e_Brushes);

  // check-allocate RNTmpData for visible objects
  Get3DEngine()->CheckCreateRNTmpData(&pEnt->m_pRNTmpData, pEnt);

	if(pEnt->GetDrawFrame(m_nRenderStackLevel) == nEngineFrameID)
    return;

  pEnt->SetDrawFrame( nEngineFrameID, m_nRenderStackLevel );

  if(nCheckOcclusion && pEnt->m_pOcNode)
    if(GetObjManager()->IsBoxOccluded(pEnt->m_WSBBox, fEntDistance*m_fInvZoomFactor, &pEnt->m_pRNTmpData->userData.m_OcclState, 
      pEnt->m_pOcNode->m_pVisArea != NULL, eoot_OBJECT))
        return;

	assert(pEnt && pEnt->m_pRNTmpData);
	if(!pEnt || !pEnt->m_pRNTmpData)
		return;
  CRenderObject * pObj = pEnt->m_pRNTmpData->userData.pRenderObject;
	assert(pObj);
	if(!pObj)
		return;

  pObj->m_fDistance = fEntDistance;

  // set lights bit mask
  if(bSunOnly)
    pObj->m_DynLMMask[m_nRenderThreadListID] = 1;
  else if(pAffectingLights)
    pObj->m_DynLMMask[m_nRenderThreadListID] = m_p3DEngine->BuildLightMask( objBox, pAffectingLights, pVisArea, (pEnt->m_dwRndFlags&ERF_OUTDOORONLY)!=0);

  pEnt->m_pRNTmpData->userData.pRenderObject = GetRenderer()->EF_GetObject_Index(pEnt->m_pRNTmpData->userData.pRenderObject->m_Id);

	//////////////////////////////////////////////////////////////////////////

	if(pEnt->m_nInternalFlags & IRenderNode::REQUIRES_NEAREST_CUBEMAP) 
	{
		if( !(pObj->m_nTextureID = CheckCachedNearestCubeProbe(pEnt) )  || !pCVars->e_CacheNearestCubePicking)			
			pObj->m_nTextureID = GetNearestCubeProbe(pAffectingLights, pVisArea, objBox);
		
		CRNTmpData::SRNUserData *pUserDataRN = (pEnt->m_pRNTmpData)? &pEnt->m_pRNTmpData->userData: 0;
		if( pUserDataRN )
			pUserDataRN->nCubeMapId = pObj->m_nTextureID;
	}

	//////////////////////////////////////////////////////////////////////////


  pEnt->Render(NULL, nThreadId);
}

void CObjManager::RenderDecalAndRoad(IRenderNode * pEnt, PodArray<CDLight*> * pAffectingLights,
                               const Vec3 & vAmbColor, const AABB & objBox, 
                               float fEntDistance,
															 bool bSunOnly, int8 nThreadId, bool nCheckOcclusion, int nEngineFrameID)
{
  //	FUNCTION_PROFILER_3DENGINE;

#ifdef _DEBUG
  const char * szName = pEnt->GetName();
  const char * szClassName = pEnt->GetEntityClassName();
#endif // _DEBUG

  // do not draw if marked to be not drawn or already drawn in this frame
  unsigned int nRndFlags = pEnt->GetRndFlags();

  if(nRndFlags&ERF_HIDDEN)
    return;

  EERType eERType = pEnt->GetRenderNodeType();

  // detect bad objects
  float fEntLengthSquared = objBox.GetSize().GetLengthSquared();
  if(eERType != eERType_Light || !_finite(fEntLengthSquared))
  {
    if(fEntLengthSquared > MAX_VALID_OBJECT_VOLUME || !_finite(fEntLengthSquared) || fEntLengthSquared<=0)
    {
      Warning("CObjManager::RenderObject: Object has invalid bbox: %s,%s, Radius = %.2f, Center = (%.1f,%.1f,%.1f)",
        pEnt->GetName(), pEnt->GetEntityClassName(), cry_sqrtf(fEntLengthSquared)*0.5f, 
        pEnt->GetBBox().GetCenter().x, pEnt->GetBBox().GetCenter().y, pEnt->GetBBox().GetCenter().z);
      return; // skip invalid objects - usually only objects with invalid very big scale will reach this point
    }
  }
  else
    pAffectingLights = NULL;

  // allocate RNTmpData for potentially visible objects
  Get3DEngine()->CheckCreateRNTmpData(&pEnt->m_pRNTmpData, pEnt);

  if(nCheckOcclusion && pEnt->m_pOcNode)
    if(GetObjManager()->IsBoxOccluded(objBox, fEntDistance*m_fInvZoomFactor, &pEnt->m_pRNTmpData->userData.m_OcclState, 
      pEnt->m_pOcNode->m_pVisArea != NULL, eoot_OBJECT))
        return;

  // skip "outdoor only" objects if outdoor is not visible
  if(GetCVars()->e_CoverageBuffer==2 && nRndFlags&ERF_OUTDOORONLY && !Get3DEngine()->GetCoverageBuffer()->IsOutdooVisible())
    return;

  if(pEnt->GetDrawFrame(m_nRenderStackLevel) == nEngineFrameID)
    return;
  pEnt->SetDrawFrame( nEngineFrameID, m_nRenderStackLevel );

  CVisArea * pVisArea = (CVisArea *)pEnt->GetEntityVisArea();

	const Vec3 & vCamPos = GetCamera().GetPosition();

  // test only near/big occluders - others will be tested on tree nodes level
  if(!objBox.IsContainPoint(vCamPos))
    if(eERType == eERType_Light || fEntDistance < pEnt->m_fWSMaxViewDist*GetCVars()->e_OcclusionCullingViewDistRatio)
      if(IsBoxOccluded(objBox, fEntDistance*m_fInvZoomFactor, &pEnt->m_pRNTmpData->userData.m_OcclState, pVisArea!=NULL, eoot_OBJECT))
        return;

  SRendParams DrawParams;  
  DrawParams.pTerrainTexInfo = NULL;
  DrawParams.dwFObjFlags = 0;
  DrawParams.fDistance = fEntDistance;
  DrawParams.AmbientColor = vAmbColor;
  DrawParams.pRenderNode = pEnt;
  DrawParams.nThreadId = nThreadId;

  // set lights bit mask
  if(bSunOnly)
  {
    DrawParams.nDLightMask = 1;
  }
  else if(pAffectingLights)
  {
    //DrawParams.restLightInfo.refPoint = m_p3DEngine->GetEntityRegisterPoint( pEnt );
    DrawParams.nDLightMask = m_p3DEngine->BuildLightMask( objBox, pAffectingLights, pVisArea, (nRndFlags&ERF_OUTDOORONLY)!=0);//, &DrawParams.restLightInfo);
  }

  DrawParams.nAfterWater = IsAfterWater(objBox.GetCenter(), vCamPos) ? 1 : 0;

  // draw bbox
  if (GetCVars()->e_BBoxes)// && eERType != eERType_Light)
  {
    RenderObjectDebugInfo(pEnt, fEntDistance, DrawParams);
  }

  DrawParams.dwFObjFlags |= FOB_TRANS_MASK;

  DrawParams.m_pVisArea	=	pVisArea;

  if(GetCVars()->e_DebugLights && (nRndFlags&ERF_SELECTED) && eERType != eERType_Light)
  {
    int nLightCount = 0;
    for(int i=0; i<32; i++)
      if(DrawParams.nDLightMask & (1<<i))
        nLightCount++;

    if(nLightCount>=GetCVars()->e_DebugLights)
      GetRenderer()->DrawLabel(objBox.GetCenter(), 2, "%d lights", nLightCount);
  }

  DrawParams.nMaterialLayers = pEnt->GetMaterialLayers();

  pEnt->Render(DrawParams);
}

void CObjManager::RenderVegetation(CVegetation * pEnt, PodArray<CDLight*> * pAffectingLights,
                               /*const Vec3 & vAmbColor, */const AABB & objBox, 
                               float fEntDistance,
															 bool bSunOnly, int8 nThreadId, SSectorTextureSet * pTerrainTexInfo, bool nCheckOcclusion, 
															 int nEngineFrameID)
{
	VEGETATION_LIGHT_PROFILER();
  //	FUNCTION_PROFILER_3DENGINE;

#ifdef _DEBUG
	const char * szName = pEnt->GetName();
	const char * szClassName = pEnt->GetEntityClassName();
#endif // _DEBUG

	// check cvars
	assert(GetCVars()->e_Vegetation);

	// check-allocate RNTmpData for visible objects
	Get3DEngine()->CheckCreateRNTmpData(&pEnt->m_pRNTmpData, pEnt);

	if(pEnt->GetDrawFrame(m_nRenderStackLevel) == nEngineFrameID)
		return;
	pEnt->SetDrawFrame( nEngineFrameID, m_nRenderStackLevel );

	CRenderObject * pObj = pEnt->m_pRNTmpData->userData.pRenderObject;

	pObj->m_fDistance = fEntDistance;
	pObj->m_nSort = fastround_positive(fEntDistance * 2.0f);

	if(nCheckOcclusion && pEnt->m_pOcNode)
		if(GetObjManager()->IsBoxOccluded(objBox, fEntDistance*m_fInvZoomFactor, &pEnt->m_pRNTmpData->userData.m_OcclState, 
			pEnt->m_pOcNode->m_pVisArea != NULL, eoot_OBJECT))
			return;

  // set lights bit mask
	if (bSunOnly)
		pObj->m_DynLMMask[m_nRenderThreadListID] = 1;
	else
		if(pAffectingLights)
			pObj->m_DynLMMask[m_nRenderThreadListID] = m_p3DEngine->BuildLightMask( objBox, pAffectingLights, NULL, true);

	if(pTerrainTexInfo)
	{
		if(pObj->m_ObjFlags & (FOB_BLEND_WITH_TERRAIN_COLOR/* | FOB_AMBIENT_OCCLUSION*/))
		{
			pObj->m_nTextureID = pTerrainTexInfo->nTex0;
			//pObj->m_nTextureID1 = pTerrainTexInfo->nTex1;
			SRenderObjData *pOD = GetRenderer()->EF_GetObjData(pObj, true);
			pOD->m_fTempVars[0] = pTerrainTexInfo->fTexOffsetX;
			pOD->m_fTempVars[1] = pTerrainTexInfo->fTexOffsetY;
			pOD->m_fTempVars[2] = pTerrainTexInfo->fTexScale;
			pOD->m_fTempVars[3] = pTerrainTexInfo->fTerrainMinZ - GetCamera().GetPosition().z;
			pOD->m_fTempVars[4] = pTerrainTexInfo->fTerrainMaxZ - GetCamera().GetPosition().z;
		}
	}

	pEnt->m_pRNTmpData->userData.pRenderObject = GetRenderer()->EF_GetObject_Index(pEnt->m_pRNTmpData->userData.pRenderObject->m_Id);

	pEnt->Render(NULL, nThreadId, pTerrainTexInfo);
}

#include "CryThread.h"

int16 CObjManager::GetNearestCubeProbe(PodArray<CDLight*> * pAffectingLights, IVisArea * pVisArea, const AABB & objBox, bool bSpecular )
{
	// Only used for alpha blended geometry - but still should be optimized further

	PodArray<IRenderNode*> pObjLightInstances; // final should be cube map instances

	if( !pVisArea )
	{
		int nObjTreeCount = Get3DEngine()->m_pObjectsTree.Count();
		for(int nCurrTree=0; nCurrTree<nObjTreeCount; ++nCurrTree)
			if(Get3DEngine()->IsSegmentSafeToUse(nCurrTree))
				Get3DEngine()->m_pObjectsTree[nCurrTree]->GetObjectsByType(pObjLightInstances, eERType_Light, &objBox); 
	}
	else  
		Get3DEngine()->GetVisAreaManager()->GetObjectsByType(pObjLightInstances, eERType_Light, &objBox);

	int32 nNearestLightID = -1;
	int32 nNearestGlobalLightID = -1;
	float fMinDistance = 65535.0f; // 256 meters limit for local CMs
	float fMinDistanceGlobal = 1e9f;

	float fObjBboxRadiusSqr = objBox.GetRadiusSqr();

	int32 nLightInstancesCount = pObjLightInstances.Count();
	for(int32 nLightID = 0; nLightID < nLightInstancesCount; ++nLightID)
	{
		CLightEntity *pLightEnt = (CLightEntity *)pObjLightInstances[nLightID];
		if( pLightEnt )
		{
			CDLight *pLight = &pLightEnt->m_light;
			float fRadiusSqr = pLight->m_fRadius * pLight->m_fRadius;

			if( (pLight->m_Flags & DLF_DEFERRED_CUBEMAPS) )
			{
				const float fGlobalCubeRadius = 99999.f; //Artists use 100000 for global CMs
				float fCurrentDist = objBox.GetDistanceSqr(pLight->m_Origin);

				if( pLight->m_fRadius > fGlobalCubeRadius && fCurrentDist < fMinDistanceGlobal)
				{
					fMinDistanceGlobal = fCurrentDist;
					nNearestGlobalLightID = nLightID;
				}
				else
					if( fCurrentDist < fMinDistance && fCurrentDist < fRadiusSqr && fRadiusSqr > fObjBboxRadiusSqr * 0.5f) 
					{
						fMinDistance = fCurrentDist;
						nNearestLightID = nLightID;
					}
			}
		}
	}			

	if( nNearestLightID >= 0 || nNearestGlobalLightID >= 0)
	{				
		int nLightID = (nNearestLightID >= 0)? nNearestLightID : nNearestGlobalLightID;
		CLightEntity *pNearestLight = (CLightEntity *)pObjLightInstances[nLightID];

		ITexture *pTexCM = bSpecular ? pNearestLight->m_light.GetSpecularCubemap() : pNearestLight->m_light.GetDiffuseCubemap();
		// Return cubemap ID or -1 if invalid
		return (pTexCM && pTexCM->GetTextureType() >= eTT_Cube) ? pTexCM->GetTextureID() : -1;
	}

	// No cubemap found
	return 0;
}


void CObjManager::RenderObject(IRenderNode * pEnt, PodArray<CDLight*> * pAffectingLights,
                               const Vec3 & vAmbColor, const AABB & objBox, 
                               float fEntDistance,
															 bool bSunOnly, EERType eERType, int8 nThreadId, int nEngineFrameID)
{
  FUNCTION_PROFILER_3DENGINE;

	const CVars * pCVars = GetCVars();

#ifdef _DEBUG
  const char * szName = pEnt->GetName();
  const char * szClassName = pEnt->GetEntityClassName();
#endif // _DEBUG

  // do not draw if marked to be not drawn or already drawn in this frame
  unsigned int nRndFlags = pEnt->GetRndFlags();

  if(nRndFlags&ERF_HIDDEN)
    return;

#ifndef _RELEASE
  // check cvars
  switch(eERType)
  {
  case eERType_Brush:
    if(!GetCVars()->e_Brushes) return;
    break;
  case eERType_Vegetation:
    assert(0);
    break;
  case eERType_ParticleEmitter:
    if(!GetCVars()->e_Particles) return;
    break;
  case eERType_Decal:
    if(!GetCVars()->e_Decals) return;
    break;
  case eERType_VoxelObject:
    if(!GetCVars()->e_Voxel) return;
    break;
  case eERType_WaterWave:
    if(!GetCVars()->e_WaterWaves) return;
    break;
	case eERType_WaterVolume:
		if(!GetCVars()->e_WaterVolumes) return;
		break;
  case eERType_Light:
	case eERType_LightPropagationVolume:
	case eERType_LightShape:
    if(!GetCVars()->e_DynamicLights || !GetCVars()->e_Entities) return;
    break;
  case eERType_Road:
    if(!GetCVars()->e_Roads) return;
    break;
	case eERType_Cloud:
	case eERType_DistanceCloud:
		if(!GetCVars()->e_Clouds) return;
		break;
  default:
    if(!GetCVars()->e_Entities) return;
    break;
  }

  // detect bad objects
  float fEntLengthSquared = objBox.GetSize().GetLengthSquared();
  if(eERType != eERType_Light || !_finite(fEntLengthSquared))
  {
    if(fEntLengthSquared > MAX_VALID_OBJECT_VOLUME || !_finite(fEntLengthSquared) || fEntLengthSquared<=0)
    {
      Warning("CObjManager::RenderObject: Object has invalid bbox: %s, %s, Radius = %.2f, Center = (%.1f,%.1f,%.1f)",
        pEnt->GetName(), pEnt->GetEntityClassName(), cry_sqrtf(fEntLengthSquared)*0.5f, 
        pEnt->GetBBox().GetCenter().x, pEnt->GetBBox().GetCenter().y, pEnt->GetBBox().GetCenter().z);
      return; // skip invalid objects - usually only objects with invalid very big scale will reach this point
    }
  }
  else
    pAffectingLights = NULL;
#endif

	if (pEnt->m_dwRndFlags & ERF_COLLISION_PROXY || pEnt->m_dwRndFlags & ERF_RAYCAST_PROXY)
	{
		// Collision proxy is visible in Editor while in editing mode.
		if (!gEnv->IsEditor() || !gEnv->IsEditing())
		{
			if (GetCVars()->e_DebugDraw == 0)
				return; //true;
		}
	}

  // allocate RNTmpData for potentially visible objects
  Get3DEngine()->CheckCreateRNTmpData(&pEnt->m_pRNTmpData, pEnt);
	PrefetchLine(pEnt->m_pRNTmpData, 0);	//m_pRNTmpData is >128 bytes, prefetching data used in dissolveref here

#if !defined(PS3) && !defined(XENON)
  // detect already culled occluder
  if ((nRndFlags & ERF_GOOD_OCCLUDER))
  {
    if (pEnt->m_pRNTmpData->userData.m_OcclState.nLastOccludedMainFrameID == GetMainFrameID())
      return;

    if(pCVars->e_CoverageBufferDrawOccluders)
    {
      return;
    }
  }
#endif

  // skip "outdoor only" objects if outdoor is not visible
  if(pCVars->e_CoverageBuffer==2 && nRndFlags&ERF_OUTDOORONLY && !Get3DEngine()->GetCoverageBuffer()->IsOutdooVisible())
    return;

	const int nRenderStackLevel = m_nRenderStackLevel;

	const int nDrawFrame = pEnt->GetDrawFrame(nRenderStackLevel);

	if(eERType != eERType_Light)
  {
    if(nDrawFrame == nEngineFrameID)
      return;
    pEnt->SetDrawFrame( nEngineFrameID, nRenderStackLevel );
  }

  CVisArea * pVisArea = (CVisArea *)pEnt->GetEntityVisArea();

	const Vec3 & vCamPos = GetCamera().GetPosition();

  // test only near/big occluders - others will be tested on tree nodes level
	// Note: Not worth prefetch on rCam or objBox as both have been recently used by calling functions & will be in cache - Rich S
  if(!(nRndFlags&ERF_RENDER_ALWAYS) && !objBox.IsContainPoint(vCamPos))
    if(eERType == eERType_Light || fEntDistance < pEnt->m_fWSMaxViewDist*pCVars->e_OcclusionCullingViewDistRatio)
      if(IsBoxOccluded(objBox, fEntDistance*m_fInvZoomFactor, &pEnt->m_pRNTmpData->userData.m_OcclState, pVisArea!=NULL, eoot_OBJECT))
        return;

  if(eERType == eERType_Light)
  {
    if(nDrawFrame == nEngineFrameID)
      return;
    pEnt->SetDrawFrame( nEngineFrameID, nRenderStackLevel );
  }

  SRendParams DrawParams;  
  DrawParams.pTerrainTexInfo = NULL;
  DrawParams.dwFObjFlags = 0;
  DrawParams.fDistance = fEntDistance;
  DrawParams.AmbientColor = vAmbColor;
  DrawParams.pRenderNode = pEnt;
	//DrawParams.pInstance = pEnt;
	DrawParams.nThreadId = nThreadId;

	if(eERType != eERType_Light && (pEnt->m_nInternalFlags & IRenderNode::REQUIRES_NEAREST_CUBEMAP))
	{
		uint16 nCubemapTexId = 0;
		if (eERType == eERType_ParticleEmitter)
		{
			// Do not update all particle emitters every frame, use cached cubemap IDs and do a lazy update
			IParticleEmitter * pParticleEmitter = (IParticleEmitter *)pEnt;
			if (pParticleEmitter->NeedsNearestCubemapUpdate(nCubemapTexId))
			{
				nCubemapTexId = GetNearestCubeProbe(pAffectingLights, pVisArea, objBox, false);
				pParticleEmitter->CacheNearestCubemap(nCubemapTexId);
			}
		}
		else
		if( !(nCubemapTexId = CheckCachedNearestCubeProbe(pEnt) ) || !pCVars->e_CacheNearestCubePicking )					
			nCubemapTexId = GetNearestCubeProbe(pAffectingLights, pVisArea, objBox);

		CRNTmpData::SRNUserData *pUserDataRN = (pEnt->m_pRNTmpData)? &pEnt->m_pRNTmpData->userData: 0;
		if( pUserDataRN )
			pUserDataRN->nCubeMapId = nCubemapTexId;

		DrawParams.nTextureID = nCubemapTexId;
	}

  // set lights bit mask
  if(bSunOnly)
  {
    DrawParams.nDLightMask = 1;
  }
  else if(pAffectingLights)
  {
    DrawParams.nDLightMask = m_p3DEngine->BuildLightMask( objBox, pAffectingLights, pVisArea, (nRndFlags&ERF_OUTDOORONLY)!=0);
  }

  if(pCVars->e_Dissolve && !nRenderStackLevel && eERType != eERType_Light && !Get3DEngine()->IsRenderingIntoShadowMap())
  { 
		if(DrawParams.nDissolveRef = GetDissolveRef(fEntDistance, pEnt->m_fWSMaxViewDist))
    {
      DrawParams.dwFObjFlags |= FOB_DISSOLVE|FOB_DISSOLVE_OUT;
      if(DrawParams.nDissolveRef == 255)
        return;
    }
  }

  DrawParams.nAfterWater = IsAfterWater(objBox.GetCenter(), vCamPos) ? 1 : 0;

  if(nRndFlags&ERF_SELECTED)
    DrawParams.dwFObjFlags |= FOB_SELECTED;

  // get list of shadow map frustums affecting this entity
  if( (pEnt->m_nInternalFlags & IRenderNode::REQUIRES_FORWARD_RENDERING) && 
    (pCVars->e_ShadowsOnAlphaBlend || (nRndFlags & ERF_SUBSURFSCATTER)) && pCVars->e_Shadows && !nRenderStackLevel)
	{
		// TODO: proper fix for per render object shadow light mask
    DrawParams.m_ShadowMapCasters = 0;//GetShadowFrustumsList(pAffectingLights, objBox, fEntDistance,
			//DrawParams.nDLightMask, fEntDistance < m_fGSMMaxDistance);
	}

  // draw bbox
#ifndef _RELEASE
  if (pCVars->e_BBoxes)// && eERType != eERType_Light)
  {
		RenderObjectDebugInfo(pEnt, fEntDistance, DrawParams);
  }
#endif

  DrawParams.dwFObjFlags |= FOB_TRANS_MASK;

	if(pEnt->m_dwRndFlags & ERF_NO_DECALNODE_DECALS)
		DrawParams.dwFObjFlags |= FOB_NO_STATIC_DECALS;

  DrawParams.m_pVisArea	=	pVisArea;

#if !defined _RELEASE
  if(nRenderStackLevel<0 || nRenderStackLevel>=MAX_RECURSION_LEVELS)
  {
    CRY_ASSERT_MESSAGE(nRenderStackLevel >= 0 && nRenderStackLevel < MAX_RECURSION_LEVELS,"nRenderStackLevel is outside max recursions level");
  }
#endif

  if(pVisArea != m_arrLastVisArea[nRenderStackLevel])	//This reduced the number of calls to EF_GetVisAreaStencilRef() by ~90%
  {
		m_arrLastVisArea[nRenderStackLevel] = pVisArea;
		uint8 visAreaStencilRef = gEnv->pRenderer->EF_GetVisAreaStencilRef( pVisArea );
		DrawParams.nVisAreaStencilRef = visAreaStencilRef;
		m_lastVisAreaStencilRef				= visAreaStencilRef;
	}
	else
	{
		DrawParams.nVisAreaStencilRef = m_lastVisAreaStencilRef;
	}
	

#ifndef _RELEASE
  if(pCVars->e_DebugLights && (nRndFlags&ERF_SELECTED) && eERType != eERType_Light)
  {
    int nLightCount = 0;
    for(int i=0; i<32; i++)
      if(DrawParams.nDLightMask & (1<<i))
        nLightCount++;

    if(nLightCount>=pCVars->e_DebugLights)
      GetRenderer()->DrawLabel(objBox.GetCenter(), 2, "%d lights", nLightCount);
  }
#endif

  // Update wind
  if ((nRndFlags & ERF_RECVWIND) && pCVars->e_Wind)
	{
		if (pEnt->m_pRNTmpData)
		{
			if (CRenderObject* pObj = pEnt->m_pRNTmpData->userData.pRenderObject)
			{
			  if (CStatObj* pStatObj = (CStatObj*)pEnt->GetEntityStatObj())
				{
					pStatObj->SetupBending(pObj, pEnt);
				}
			}
		}
	}

  DrawParams.nMaterialLayers = pEnt->GetMaterialLayers();
	DrawParams.nLod = (uint8)pEnt->m_pRNTmpData->userData.nLod;

  pEnt->Render(DrawParams);
}

void CObjManager::RenderObjectDebugInfo(IRenderNode * pEnt, float fEntDistance, const SRendParams & DrawParams)
{
  if(Get3DEngine()->IsRenderingIntoShadowMap() || m_nRenderStackLevel)
    return;

	if (GetCVars()->e_BBoxes > 0)
	{
		ColorF color(1,1,1,1);

		if (GetCVars()->e_BBoxes == 2 && pEnt->GetRndFlags()&ERF_SELECTED)
		{
			color.a *= clamp_tpl(pEnt->GetImportance(), 0.5f, 1.f);
			float fFontSize = max(2.f - fEntDistance * 0.01f, 1.f);

			string sLabel = pEnt->GetDebugString();
			if (sLabel.empty())
			{
				sLabel.Format("%s/%s %d/%d", 
					pEnt->GetName(), pEnt->GetEntityClassName(), 
					DrawParams.nDLightMask, 0);//DrawParams.m_ShadowMapCasters.size());
			}
			GetRenderer()->DrawLabelEx(pEnt->GetBBox().GetCenter(), fFontSize, (float*)&color, true, true, "%s", sLabel.c_str());
		}

		IRenderAuxGeom* pRenAux = GetRenderer()->GetIRenderAuxGeom();
		pRenAux->SetRenderFlags(SAuxGeomRenderFlags());
		AABB	rAABB		=	pEnt->GetBBox();
		const float Bias	=	GetCVars()->e_CoverageBufferAABBExpand;
		if(Bias<0.f)
			rAABB.Expand((rAABB.max-rAABB.min)*Bias-Vec3(Bias,Bias,Bias));
		else
			rAABB.Expand(Vec3(Bias,Bias,Bias));

		pRenAux->DrawAABB(rAABB, false, color, eBBD_Faceted);
	}
}

void CObjManager::RenderObjectDebugInfo(IRenderNode * pEnt, const CRenderObject * pRendObj)
{
  if (GetCVars()->e_BBoxes > 0)
  {
    ColorF color(1,1,1,1);

    if (GetCVars()->e_BBoxes == 2 && pEnt->GetRndFlags()&ERF_SELECTED)
    {
      color.a *= clamp_tpl(pEnt->GetImportance(), 0.5f, 1.f);
      float fFontSize = max(2.f - pRendObj->m_fDistance * 0.01f, 1.f);

      string sLabel = pEnt->GetDebugString();
      if (sLabel.empty())
      {
				//SRenderObjData * pOD = GetRenderer()->EF_GetObjData(pRendObj, false);
        sLabel.Format("%s/%s %d/%d", 
          pEnt->GetName(), pEnt->GetEntityClassName(), 
					pRendObj->m_DynLMMask, 0);//pOD ? pOD->m_ShadowCasters : 0);
      }
      GetRenderer()->DrawLabelEx(pEnt->GetBBox().GetCenter(), fFontSize, (float*)&color, true, true, "%s", sLabel.c_str());
    }

    IRenderAuxGeom* pRenAux = GetRenderer()->GetIRenderAuxGeom();
    pRenAux->SetRenderFlags(SAuxGeomRenderFlags());
		AABB	rAABB		=	pEnt->GetBBox();
		const float Bias	=	GetCVars()->e_CoverageBufferAABBExpand;
		if(Bias<0.f)
			rAABB.Expand((rAABB.max-rAABB.min)*Bias-Vec3(Bias,Bias,Bias));
		else
			rAABB.Expand(Vec3(Bias,Bias,Bias));
    pRenAux->DrawAABB(rAABB, false, color, eBBD_Faceted);
  }
}

bool CObjManager::RenderStatObjIntoZBuffer(IStatObj * pStatObj, Matrix34A & objMatrix, 
																						unsigned int nRndFlags, bool bCompletellyInFrustum,
																						CCullBuffer & rCB, IMaterial * pMat)
{
#ifdef USE_OCCLUSION_PROXY
	assert(pStatObj);

	CStatObj *pCStatObj = (CStatObj*)pStatObj;

	if(!pCStatObj || pCStatObj->m_nFlags&STATIC_OBJECT_HIDDEN)
		return false;

	IRenderMesh *pRenderMeshOcclusion = pCStatObj->m_pRenderMeshOcclusion;
	if (pCStatObj->m_pLod0)
		pRenderMeshOcclusion = pCStatObj->m_pLod0->m_pRenderMeshOcclusion;

  if(!pRenderMeshOcclusion && pCStatObj->GetRenderTrisCount() && pCStatObj->GetRenderTrisCount()==12)
    pRenderMeshOcclusion = pCStatObj->GetRenderMesh(); // solids

	if (pRenderMeshOcclusion)
	{
		rCB.AddRenderMesh(pRenderMeshOcclusion, &objMatrix, Get3DEngine()->GetMaterialManager()->GetDefaultMaterial(), 
      (nRndFlags&ERF_OUTDOORONLY)!=0, bCompletellyInFrustum,true);
	}
	else
	{
		IRenderMesh *pRenderMesh = 0;
		if (pCStatObj->m_nFlags & STATIC_OBJECT_COMPOUND)
		{
			// multi-sub-objects
			for(int s=0,num = pCStatObj->SubObjectCount(); s<num; s++)
			{
				IStatObj::SSubObject &subObj = pCStatObj->SubObject(s);
				if (subObj.pStatObj && !subObj.bHidden && subObj.nType == STATIC_SUB_OBJECT_MESH)
				{
					Matrix34A subObjMatrix = objMatrix * subObj.tm;

					CStatObj *PCSubStatObj = ((CStatObj*)subObj.pStatObj);
					pRenderMesh = PCSubStatObj->m_pRenderMeshOcclusion;
					if (PCSubStatObj->m_pLod0)
						pRenderMesh = PCSubStatObj->m_pLod0->m_pRenderMeshOcclusion;

					if (pRenderMesh)
						rCB.AddRenderMesh(pRenderMesh, &subObjMatrix, pMat, (nRndFlags&ERF_OUTDOORONLY)!=0, bCompletellyInFrustum,true);
				}
			}
		}
	}

#endif
	return true;
}

bool CObjManager::RayRenderMeshIntersection(IRenderMesh * pRenderMesh, const Vec3 & vInPos, const Vec3 & vInDir, Vec3 & vOutPos, Vec3 & vOutNormal,bool bFastTest,IMaterial * pMat)
{
	FUNCTION_PROFILER_3DENGINE;

	struct MeshLock
	{
		MeshLock(IRenderMesh* m = 0) : mesh(m)
		{
			if( m ) m->LockForThreadAccess();
		}
		~MeshLock()
		{
			if( mesh )
			{
				mesh->UnlockStream(VSF_GENERAL);
				mesh->UnlockIndexStream();
				mesh->UnLockForThreadAccess();
			}
		}
	private:
		IRenderMesh* mesh;
	};

	MeshLock rmLock(pRenderMesh);

	// get position offset and stride
	int nPosStride = 0;
	byte * pPos  = pRenderMesh->GetPosPtr(nPosStride, FSL_READ);

	// get indices
	uint16 *pInds = pRenderMesh->GetIndexPtr(FSL_READ);
	int nInds = pRenderMesh->GetIndicesCount();
	assert(nInds%3 == 0);

	float fClosestHitDistance = -1;

	Lineseg l0(vInPos+vInDir, vInPos-vInDir);
	Lineseg l1(vInPos-vInDir, vInPos+vInDir);

	Vec3 vHitPoint(0,0,0);

//	bool b2DTest = fabs(vInDir.x)<0.001f && fabs(vInDir.y)<0.001f;

	// test tris
	PodArray<CRenderChunk>& Chunks = pRenderMesh->GetChunks();
	for (int nChunkId=0; nChunkId<Chunks.Count(); nChunkId++)
	{
		CRenderChunk * pChunk = &Chunks[nChunkId];
		if(pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
			continue;

		if(pMat)
		{
			const SShaderItem &shaderItem = pMat->GetShaderItem(pChunk->m_nMatID);
			if (!shaderItem.m_pShader || shaderItem.m_pShader->GetFlags() & EF_NODRAW)
				continue;
		}

		AABB triBox;

		int nLastIndexId = pChunk->nFirstIndexId + pChunk->nNumIndices;
		for(int i=pChunk->nFirstIndexId; i<nLastIndexId; i+=3)
		{
			assert(pInds[i+0]<pRenderMesh->GetVerticesCount());
			assert(pInds[i+1]<pRenderMesh->GetVerticesCount());
			assert(pInds[i+2]<pRenderMesh->GetVerticesCount());

			// get vertices
			const Vec3 v0 = (*(Vec3*)&pPos[nPosStride*pInds[i+0]]);
			const Vec3 v1 = (*(Vec3*)&pPos[nPosStride*pInds[i+1]]);
			const Vec3 v2 = (*(Vec3*)&pPos[nPosStride*pInds[i+2]]);
/*
			if(b2DTest)
			{
				triBox.min = triBox.max = v0;
				triBox.Add(v1);
				triBox.Add(v2);
				if(	vInPos.x < triBox.min.x || vInPos.x > triBox.max.x || vInPos.y < triBox.min.y || vInPos.y > triBox.max.y )
					continue;
			}
*/
			// make line triangle intersection
			if(	Intersect::Lineseg_Triangle(l0, v0, v1, v2, vHitPoint) ||
					Intersect::Lineseg_Triangle(l1, v0, v1, v2, vHitPoint) )
			{ 
				if (bFastTest)
					return (true);

				float fDist = vHitPoint.GetDistance(vInPos);
				if(fDist<fClosestHitDistance || fClosestHitDistance<0)
				{
					fClosestHitDistance = fDist;
					vOutPos = vHitPoint;
					vOutNormal = (v1-v0).Cross(v2-v0);
				}
			}
		}
	}

	if(fClosestHitDistance>=0)
	{ 
		vOutNormal.Normalize();
		return true;
	}

	return false;
}

bool CObjManager::RayStatObjIntersection(IStatObj * pStatObj, const Matrix34 & objMatrix, IMaterial * pMat,
																				 Vec3 vStart, Vec3 vEnd, Vec3 & vClosestHitPoint, float & fClosestHitDistance, bool bFastTest)
{
	assert(pStatObj);

	CStatObj *pCStatObj = (CStatObj*)pStatObj;

	if(!pCStatObj || pCStatObj->m_nFlags&STATIC_OBJECT_HIDDEN)
		return false;

	Matrix34 matInv = objMatrix.GetInverted();
	Vec3 vOSStart = matInv.TransformPoint(vStart);
	Vec3 vOSEnd = matInv.TransformPoint(vEnd);
	Vec3 vOSHitPoint(0,0,0), vOSHitNorm(0,0,0);

	Vec3 vBoxHitPoint;
	if(!Intersect::Ray_AABB(Ray(vOSStart, vOSEnd-vOSStart), pCStatObj->GetAABB(), vBoxHitPoint))
		return false;

	bool bHitDetected = false;

	if(IRenderMesh *pRenderMesh = pStatObj->GetRenderMesh())
	{
		if(CObjManager::RayRenderMeshIntersection(pRenderMesh, vOSStart, vOSEnd-vOSStart, vOSHitPoint, vOSHitNorm, bFastTest, pMat))
		{
			bHitDetected = true;
			Vec3 vHitPoint = objMatrix.TransformPoint(vOSHitPoint);
			float fDist = vHitPoint.GetDistance(vStart);
			if(fDist<fClosestHitDistance)
			{
				fClosestHitDistance = fDist;
				vClosestHitPoint = vHitPoint;
			}
		}
	}
	else
	{
		// multi-sub-objects
		for(int s=0,num = pCStatObj->SubObjectCount(); s<num; s++)
		{
			IStatObj::SSubObject &subObj = pCStatObj->SubObject(s);
			if (subObj.pStatObj && !subObj.bHidden && subObj.nType == STATIC_SUB_OBJECT_MESH)
			{
				Matrix34 subObjMatrix = objMatrix * subObj.tm;
				if(RayStatObjIntersection(subObj.pStatObj, subObjMatrix, pMat, vStart, vEnd, vClosestHitPoint, fClosestHitDistance, bFastTest))
					bHitDetected = true;
			}
		}
	}

	return bHitDetected;
}

uint8 CObjManager::GetDissolveRef(float fDist, float fMVD)
{
	float fDissolveDist = 1.0f / CLAMP(0.1f*fMVD, GetFloatCVar(e_DissolveDistMin), GetFloatCVar(e_DissolveDistMax));
    
	return (uint8)SATURATEB((1.0f+(fDist-fMVD)*fDissolveDist)*255.f);
}

float CObjManager::GetLodDistDissolveRef(SLodDistDissolveTransitionState * pState, float curDist, int nNewLod)
{
  if (!pState->fStartDist)
  {
    pState->fStartDist = curDist;
		pState->nOldLod = nNewLod;
		pState->nNewLod = nNewLod;

		pState->bFarside = (pState->nNewLod < pState->nOldLod && pState->nNewLod != -1) || pState->nOldLod == -1;
  }
  else if (pState->nNewLod != nNewLod)
  {
		pState->nNewLod = nNewLod;
		pState->fStartDist = curDist;
		
		pState->bFarside = (pState->nNewLod < pState->nOldLod && pState->nNewLod != -1) || pState->nOldLod == -1;
  }
	else if ((pState->nOldLod != pState->nNewLod))
	{
		// transition complete
		if(
			(!pState->bFarside && curDist - pState->fStartDist > GetFloatCVar(e_DissolveDistband)) ||
			(pState->bFarside && pState->fStartDist - curDist > GetFloatCVar(e_DissolveDistband))
			)
		{
			pState->nOldLod = pState->nNewLod;
		}
		// with distance based transitions we can always 'fail' back to the previous LOD.
		else if(
			(!pState->bFarside && curDist < pState->fStartDist) ||
			(pState->bFarside && curDist > pState->fStartDist)
			)
		{
			pState->nNewLod = pState->nOldLod;
		}
	}

	// don't dissolve in zoom mode
	if(!m_nRenderStackLevel && m_nZoomMode)
	{
		pState->fStartDist = curDist + (pState->bFarside ? 1 : -1) * GetFloatCVar(e_DissolveDistband);
		pState->nOldLod = pState->nNewLod;
		return 0.0f;
	}

	if (pState->nOldLod == pState->nNewLod)
	{
		return 0.f;
	}
	else
	{
		if(pState->bFarside)
			return SATURATE(((pState->fStartDist - curDist)*m_fInvDissolveDistBand));
		else
			return SATURATE(((curDist - pState->fStartDist)*m_fInvDissolveDistBand));
	}
}
